/*
 * iuiu.js
 * http://github.com/KumaWang/iuiu.js/
 *
 * Copyright 2018 KumaWang
 * Released under the MIT license
 */
var L2D = (function() {

// src/1-1.require.js
//使用沙箱模式防止污染外面的变量
; (function () {
    //让外面可以只能访问到require变量
    window.require = require;
    //定义一个加载模块的方法
    function require(moduleName, callback) {
        //创建加载模块的具体实现类
        var requireHelper = new RequireHelper(moduleName, callback);
        //调用加载模块类的load方法加载模块
        requireHelper.load();
    }
    //存储已加载模块的信息
    var moduleList = [];

    //创建一个实体类,给传进来的属性赋值
    function RequireHelper(moduleName, callback) {
        this.moduleName = moduleName;
        this.callback = callback;
    }

    //给模块加载实现类的原型添加方法
    RequireHelper.prototype = {
        //加载模块
        load: function () {
            var that = this;
            var moduleName = that.moduleName;
            if (that.isLoad()) {//模块已被加载(资源优化:已经请求的模块不要再次加载)
                var moduleInfo = that.getModuleInfo();//获取模块的描述信息
                if (moduleList.isLoad) {//如果模块资源已加载完成
                    if(that.callback) that.callback();//可以放心的调用模块对应的回调函数
                } else {//模块资源没加载完
                    var oldCallback = moduleInfo.callback;//取出之前的回调函数
                    moduleInfo.callback = function () {//追加回调函数
                        oldCallback();
                        if(that.callback) that.callback();
                    };
                }
            } else {//模块没有加载
                var script = document.createElement("script");
                script.src = that.moduleName;
                document.getElementsByTagName("head")[0].appendChild(script);//加载模块
                var moduleInfo = {
                    moduleName: that.moduleName, isLoad: false, callback: function () {
                        if(that.callback) that.callback();
                    }
                };//添加模块的描述信息
                script.onload = function () {
                    moduleInfo.callback();//执行模块对应的回调函数
                    moduleInfo.isLoad = true;//标识模块资源被加载完成
                }
            }
        },
        //判断指定模块是否加载
        isLoad: function () {
            return this.getModuleInfo() == null ? false : true;            
        },
        //根据模块名称获取模块信息
        getModuleInfo: function () {
            for (var i = 0; i < moduleList.length; i++) {
                if (that.moduleName == moduleList[i].name) {
                    return moduleList;
                }
            }
            return null;
        }
    };

})(window)
// src/1-2.loader.js
var parseINIString = function (data){ 
    var regex = { 
        section: /^\s*\[\s*([^\]]*)\s*\]\s*$/, 
        param: /^\s*([\w\.\-\_]+)\s*=\s*(.*?)\s*$/, 
        comment: /^\s*;.*$/ 
    }; 
    var value = {}; 
    var lines = data.split(/\r\n|\r|\n/); 
    var section = null; 
    lines.forEach(function(line){ 
    if(regex.comment.test(line)){ 
        return; 
    }else if(regex.param.test(line)){ 
        var match = line.match(regex.param); 
        if(section){ 
            value[section][match[1]] = match[2]; 
        }else{ 
            value[match[1]] = match[2]; 
        } 
    }else if(regex.section.test(line)){ 
        var match = line.match(regex.section); 
        value[match[1]] = {}; 
        section = match[1]; 
    }else if(line.length == 0 && section){ 
        section = null; 
    }; 
    });
    
    return value; 
}

function IniLoader(loader) {
    this.loader = loader;
};
IniLoader.prototype = {
    responseType : 'text',
    load : function(buffer, params) {
        return parseINIString(buffer);
    }
};

function PackageLoader(loader) {
    this.loader = loader;
}
PackageLoader.prototype = {
    responseType : 'arraybuffer',
    load : function(buffer, params) {
        if(buffer) {
            var content = {};
            var dataView = new DataView(buffer);
            var originalBuffer = readHeader(content, dataView);
            
            // 解压缩文件
            if(content.flags == 1) {
                var compressed = new Uint8Array(buffer, originalBuffer.position, originalBuffer.length);
                var decompressed = lz4.decompress(compressed)
                var arrayBuffer = new ArrayBuffer(decompressed.length);
                for(var i = 0; i < decompressed.length; i++) {
                    arrayBuffer[i] = decompressed[i];
                }
                
                originalBuffer = new BinaryReader(new DataView(arrayBuffer), 0, arrayBuffer.length);
            }

            // 读取实际内容
            readContent(content, originalBuffer);
        }
        else {
            // 失效的资源
            //content.valid = false;
            //content.errorMessage = '无效的资产源:' + content.src;
        }
    },
    readHeader : function(content, buffer) {
        var br = new BinaryReader(buffer);
        // 头校验
        var r = br.readChar();
        var e = br.readChar();
        var s = br.readChar();

        if(r != 'm' || e != 'r' || s != 'f') {
            throw '存在无效包文件';
        }

        // 读取平台
        var platform = br.readByte();
        
        // 读取文件格式
        var format = br.readByte();
        
        // 读取flags
        var flags = br.readByte();
        
        // 读取内容大小
        var contentSize = br.readInt32();

        // 预存数据
        var holdSize = br.readInt32();

        content.platform = platform;
        content.format = format;
        content.flags = flags;
        content.contentSize = contentSize;

        // 返回压缩数据大小
        return new BinaryReader(buffer, br.position, content.contentSize);
    },
    readContent : function(content, buffer) {
        var header = {};
        header.name = buffer.readString();
        header.version = {};
        header.version.major = buffer.readInt32();
        header.version.minor = buffer.readInt32();
        header.version.build = buffer.readInt32();
        header.version.revision = buffer.readInt32();
        
        var iconData = buffer.readString();
        var description = buffer.readString();
        var references = [];
        var files = [];
        
        var count = buffer.readInt32();
        for(var i = 0; i < count; i++) {
            var header2 = {};
            header2.name = buffer.readString();
            header2.version = {};
            header2.version.major = buffer.readInt32();
            header2.version.minor = buffer.readInt32();
            header2.version.build = buffer.readInt32();
            header2.version.revision = buffer.readInt32();
            
            references.push({ header : header2 });
        }
        
        count = buffer.readInt32();
        for(var i = 0; i < count;i ++) {
            var inculde = buffer.readString();
            var data = buffer.readString();
            files.push({ inculde : inculde, data : data });
        }
        
        content.header = header;
        //content.description = description;
        //content.
        content.files = files;
        content.references = reference;
    }
};

function JsonLoader(loader) {
    this.loader = loader;
}
JsonLoader.prototype = {
    responseType : 'text',
    load : function(buffer, params, entry) {
        return JSON.parse(buffer);
    },
	create: function() {
		return null;
	}
}

function Loader(domain) {
    this.domain = domain;
    //this.checklist = window.localStorage.domain
    
    // modes
    this.loaders = {};
    this.addMode('ini', new IniLoader(this));
    this.addMode('json', new JsonLoader(this));
}

Loader.prototype = {
    // ### .addMode(name, loader)
    // @param loader
    //          method load
    addMode : function(name, loader) {
        this.loaders[name] = loader;
    },
    
    // ### .load(fileName[, type])
    // @param type
    //          content
    //          ini 
    //          image
    load : function(fileName, userToken, callback, params) {
        var scope = this;
        var fileNameExt = fileName.lastIndexOf(".");//取到文件名开始到最后一个点的长度
        var fileNameLength = fileName.length;//取到文件名长度
        var fileFormat = fileName.substring(fileNameExt + 1, fileNameLength);//截
        
        var type = fileFormat;
        var loader = this.loaders[type];
        
        var content =  { status : 'error', params : params, callbacks : [], content : loader.create() };          
        if(content.status == 'error') {            
            if(loader) {
                if(callback) content.callbacks.push(callback);
                
                content.src = fileName;
                content.status = 'loading';
                content.userToken = userToken;
                content.type = type;
                if(loader.responseType) {
                    var request = new XMLHttpRequest();
                    request.responseType = loader.responseType;
                    request.open("GET", (this.domain != null ? this.domain + '/' : '') + fileName); // + (cache ? '' : '?' + new Date().toString()), true);
                    request.content = content;
                    request.loader = loader;
                    if(!request.loader) {
                        throw 'no dencoder';
                    }
                    
                    request.onload = function(e) {
                        var loader = e.currentTarget.loader;
                        var content = e.currentTarget.content;
                        try {
                            //content.md5 = CryptoJS.MD5(e.currentTarget.response);
                            content.content = loader.load(e.currentTarget.response, content.params, content.content);
                            content.status = 'loaded';
                        }
                        catch(error) {
							console.log(error);
                            content.status = 'error';
                            if(content.onerror) 
                                content.onerror(content);
                        }
                            
                        if(content.status == 'loaded') {
                            for(var i = 0; i < content.callbacks.length; i++) {
                                content.callbacks[i](content);
                            }
                            content.callbacks = [];
                        }
                    };
                    request.onerror = function(e) {
                        var content = e.currentTarget.content;
                        content.status = 'error';
                        if(content.onerror) 
                            content.onerror(content);
                        //content.errorMessage = 'Error ' + e.target.status + ' occurred while receiving the document.'
                    };
                    request.send();
                    
                    // 如果是同步资源
                    if(request.loader.sync) {
                        // 暂停循环
                        
                    }
                } else {
                    throw 'responseType';
                    //content.content = loader.load(fileName);
                }
            } else {
                throw 'unkonwn response type';
            }
        }
        else if(content.status == "loading") {
            if(callback) content.callbacks.push(callback);
        }
        else if(content.status == 'loaded') {
            if(callback) callback(content);
        }
        
        return content.content;
    }
};
// src/1-3.keyboard.js
const isff = typeof navigator !== 'undefined' ? navigator.userAgent.toLowerCase().indexOf('firefox') > 0 : false;

// 绑定事件
function addEvent(object, event, method) {
  if (object.addEventListener) {
    object.addEventListener(event, method, false);
  } else if (object.attachEvent) {
    object.attachEvent(`on${event}`, () => { method(window.event); });
  }
}

// 修饰键转换成对应的键码
function getMods(modifier, key) {
  const mods = key.slice(0, key.length - 1);
  for (let i = 0; i < mods.length; i++) mods[i] = modifier[mods[i].toLowerCase()];
  return mods;
}

// 处理传的key字符串转换成数组
function getKeys(key) {
  if (!key) key = '';

  key = key.replace(/\s/g, ''); // 匹配任何空白字符,包括空格、制表符、换页符等等
  const keys = key.split(','); // 同时设置多个快捷键，以','分割
  let index = keys.lastIndexOf('');

  // 快捷键可能包含','，需特殊处理
  for (; index >= 0;) {
    keys[index - 1] += ',';
    keys.splice(index, 1);
    index = keys.lastIndexOf('');
  }

  return keys;
}

// 比较修饰键的数组
function compareArray(a1, a2) {
  const arr1 = a1.length >= a2.length ? a1 : a2;
  const arr2 = a1.length >= a2.length ? a2 : a1;
  let isIndex = true;

  for (let i = 0; i < arr1.length; i++) {
    if (arr2.indexOf(arr1[i]) === -1) isIndex = false;
  }
  return isIndex;
}

const _keyMap = { // 特殊键
  backspace: 8,
  tab: 9,
  clear: 12,
  enter: 13,
  return: 13,
  esc: 27,
  escape: 27,
  space: 32,
  left: 37,
  up: 38,
  right: 39,
  down: 40,
  del: 46,
  delete: 46,
  ins: 45,
  insert: 45,
  home: 36,
  end: 35,
  pageup: 33,
  pagedown: 34,
  capslock: 20,
  '?': 20,
  ',': 188,
  '.': 190,
  '/': 191,
  '`': 192,
  '-': isff ? 173 : 189,
  '=': isff ? 61 : 187,
  ';': isff ? 59 : 186,
  '\'': 222,
  '[': 219,
  ']': 221,
  '\\': 220,
};

const _modifier = { // 修饰键
  '?': 16,
  shift: 16,
  '?': 18,
  alt: 18,
  option: 18,
  '?': 17,
  ctrl: 17,
  control: 17,
  '?': isff ? 224 : 91,
  cmd: isff ? 224 : 91,
  command: isff ? 224 : 91,
};
const modifierMap = {
  16: 'shiftKey',
  18: 'altKey',
  17: 'ctrlKey',
};
const _mods = { 16: false, 18: false, 17: false };
const _handlers = {};

// F1~F12 特殊键
for (let k = 1; k < 20; k++) {
  _keyMap[`f${k}`] = 111 + k;
}

// 兼容Firefox处理
modifierMap[isff ? 224 : 91] = 'metaKey';
_mods[isff ? 224 : 91] = false;


let _downKeys = []; // 记录摁下的绑定键

let _scope = 'all'; // 默认热键范围
const elementHasBindEvent = []; // 已绑定事件的节点记录

// 返回键码
const code = x => _keyMap[x.toLowerCase()] || _modifier[x.toLowerCase()] || x.toUpperCase().charCodeAt(0);

// 设置获取当前范围（默认为'所有'）
function setScope(scope) { _scope = scope || 'all'; }
// 获取当前范围
function getScope() { return _scope || 'all'; }
// 获取摁下绑定键的键值
function getPressedKeyCodes() { return _downKeys.slice(0); }

// 表单控件控件判断 返回 Boolean
// hotkey is effective only when filter return true
function filter(event) {
  const target = event.target || event.srcElement;
  const tagName = target.tagName;
  let flag = true;
  // ignore: isContentEditable === 'true', <input> and <textarea> when readOnly state is false, <select>
  if (
    target.isContentEditable ||
    tagName === 'TEXTAREA' ||
    ((tagName === 'INPUT' || tagName === 'TEXTAREA') && !target.readOnly)
  ) {
    flag = false;
  }
  return flag;
}

// 判断摁下的键是否为某个键，返回true或者false
function isPressed(keyCode) {
  if (typeof (keyCode) === 'string') {
    keyCode = code(keyCode); // 转换成键码
  }
  return _downKeys.indexOf(keyCode) !== -1;
}


// 循环删除handlers中的所有 scope(范围)
function deleteScope(scope, newScope) {
  let handlers;
  let i;

  // 没有指定scope，获取scope
  if (!scope) scope = getScope();

  for (const key in _handlers) {
    if (Object.prototype.hasOwnProperty.call(_handlers, key)) {
      handlers = _handlers[key];
      for (i = 0; i < handlers.length;) {
        if (handlers[i].scope === scope) handlers.splice(i, 1);
        else i++;
      }
    }
  }

  // 如果scope被删除，将scope重置为all
  if (getScope() === scope) setScope(newScope || 'all');
}

// 清除修饰键
function clearModifier(event) {
  let key = event.keyCode || event.which || event.charCode;
  const i = _downKeys.indexOf(key);

  // 从列表中清除按压过的键
  if (i >= 0) {
    _downKeys.splice(i, 1);
  }
  // 特殊处理 cmmand 键，在 cmmand 组合快捷键 keyup 只执行一次的问题
  if (event.key && event.key.toLowerCase() === 'meta') {
    _downKeys.splice(0, _downKeys.length);
  }

  // 修饰键 shiftKey altKey ctrlKey (command||metaKey) 清除
  if (key === 93 || key === 224) key = 91;
  if (key in _mods) {
    _mods[key] = false;

    // 将修饰键重置为false
    for (const k in _modifier) if (_modifier[k] === key) hotkeys[k] = false;
  }
}

// 解除绑定某个范围的快捷键
function unbind(key, scope, method) {
  const multipleKeys = getKeys(key);
  let keys;
  let mods = [];
  let obj;
  // 通过函数判断，是否解除绑定
  // https://github.com/jaywcjlove/hotkeys/issues/44
  if (typeof scope === 'function') {
    method = scope;
    scope = 'all';
  }

  for (let i = 0; i < multipleKeys.length; i++) {
    // 将组合快捷键拆分为数组
    keys = multipleKeys[i].split('+');

    // 记录每个组合键中的修饰键的键码 返回数组
    if (keys.length > 1) {
      mods = getMods(_modifier, keys);
    } else {
      mods = [];
    }

    // 获取除修饰键外的键值key
    key = keys[keys.length - 1];
    key = key === '*' ? '*' : code(key);

    // 判断是否传入范围，没有就获取范围
    if (!scope) scope = getScope();

    // 如何key不在 _handlers 中返回不做处理
    if (!_handlers[key]) return;

    // 清空 handlers 中数据，
    // 让触发快捷键键之后没有事件执行到达解除快捷键绑定的目的
    for (let r = 0; r < _handlers[key].length; r++) {
      obj = _handlers[key][r];
      // 通过函数判断，是否解除绑定，函数相等直接返回
      const isMatchingMethod = method ? obj.method === method : true;

      // 判断是否在范围内并且键值相同
      if (
        isMatchingMethod &&
        obj.scope === scope &&
        compareArray(obj.mods, mods)
      ) {
        _handlers[key][r] = {};
      }
    }
  }
}

// 对监听对应快捷键的回调函数进行处理
function eventHandler(handler, scope) {
  let modifiersMatch;

  // 看它是否在当前范围
  if (handler.scope === scope || handler.scope === 'all') {
    // 检查是否匹配修饰符（如果有返回true）
    modifiersMatch = handler.mods.length > 0;

    for (const y in _mods) {
      if (Object.prototype.hasOwnProperty.call(_mods, y)) {
        if (
          (!_mods[y] && handler.mods.indexOf(+y) > -1) ||
          (_mods[y] && handler.mods.indexOf(+y) === -1)
        ) modifiersMatch = false;
      }
    }

    // 调用处理程序，如果是修饰键不做处理
    if (
      (handler.mods.length === 0 && !_mods[16] && !_mods[18] && !_mods[17] && !_mods[91]) ||
      modifiersMatch ||
      handler.shortcut === '*'
    ) {
      handler.method(handler);
    }
  }
}


// 处理keydown事件
function dispatch(event) {
  const asterisk = _handlers['*'];
  let key = event.keyCode || event.which || event.charCode;

  // 表单控件过滤 默认表单控件不触发快捷键
  if (!hotkeys.filter.call(this, event)) return;

  // Gecko(Firefox)的command键值224，在Webkit(Chrome)中保持一致
  // Webkit左右command键值不一样
  if (key === 93 || key === 224) key = 91;

  // Collect bound keys
  // If an Input Method Editor is processing key input and the event is keydown, return 229.
  // https://stackoverflow.com/questions/25043934/is-it-ok-to-ignore-keydown-events-with-keycode-229
  // http://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html
  if (_downKeys.indexOf(key) === -1 && key !== 229) _downKeys.push(key);

  if (key in _mods) {
    _mods[key] = true;

    // 将特殊字符的key注册到 hotkeys 上
    for (const k in _modifier) {
      if (_modifier[k] === key) hotkeys[k] = true;
    }

    if (!asterisk) return;
  }

  // 将modifierMap里面的修饰键绑定到event中
  for (const e in _mods) {
    if (Object.prototype.hasOwnProperty.call(_mods, e)) {
      _mods[e] = event[modifierMap[e]];
    }
  }
}

// 判断 element 是否已经绑定事件
function isElementBind(element) {
  return elementHasBindEvent.indexOf(element) > -1;
}

function update() {
  const asterisk = _handlers['*'];
  
  // 获取范围 默认为all
  const scope = getScope();

  // 对任何快捷键都需要做的处理
  if (asterisk) {
    for (let i = 0; i < asterisk.length; i++) {
      if (asterisk[i].scope === scope) {
        eventHandler(asterisk[i], scope);
      }
    }
  }
  // key 不在_handlers中返回
  // if (!(key in _handlers)) return;
  for (const key in _handlers) {
    if (Object.prototype.hasOwnProperty.call(_handlers, key)) {
      for (let i = 0; i < _handlers[key].length; i++) {
        if (_handlers[key][i].key) {
          const keyShortcut = _handlers[key][i].key.split('+');
          let _downKeysCurrent = []; // 记录当前按键键值
          for (let a = 0; a < keyShortcut.length; a++) {
            _downKeysCurrent.push(code(keyShortcut[a]));
          }
          _downKeysCurrent = _downKeysCurrent.sort();
          if (_downKeysCurrent.join('') === _downKeys.sort().join('')) {
            // 找到处理内容
            eventHandler(_handlers[key][i], scope);
          }
        }
      }
    }
  }
}

function hotkeys(key, option, method) {
  const keys = getKeys(key); // 需要处理的快捷键列表
  let mods = [];
  let scope = 'all'; // scope默认为all，所有范围都有效
  let element = document; // 快捷键事件绑定节点
  let i = 0;
  let keyup = false;
  let keydown = true;

  // 对为设定范围的判断
  if (method === undefined && typeof option === 'function') {
    method = option;
  }

  if (Object.prototype.toString.call(option) === '[object Object]') {
    if (option.scope) scope = option.scope; // eslint-disable-line
    if (option.element) element = option.element; // eslint-disable-line
    if (option.keyup) keyup = option.keyup; // eslint-disable-line
    if (option.keydown !== undefined) keydown = option.keydown; // eslint-disable-line
  }

  if (typeof option === 'string') scope = option;

  // 对于每个快捷键进行处理
  for (; i < keys.length; i++) {
    key = keys[i].split('+'); // 按键列表
    mods = [];

    // 如果是组合快捷键取得组合快捷键
    if (key.length > 1) mods = getMods(_modifier, key);

    // 将非修饰键转化为键码
    key = key[key.length - 1];
    key = key === '*' ? '*' : code(key); // *表示匹配所有快捷键

    // 判断key是否在_handlers中，不在就赋一个空数组
    if (!(key in _handlers)) _handlers[key] = [];
    _handlers[key].push({
      keyup,
      keydown,
      scope,
      mods,
      shortcut: keys[i],
      method,
      key: keys[i],
    });
  }
  // 在全局document上设置快捷键
  if (typeof element !== 'undefined' && !isElementBind(element) && window) {
    elementHasBindEvent.push(element);
    addEvent(element, 'keydown', (e) => {
      dispatch(e);
      event.preventDefault();
	  event.stopPropagation();
 	  event.cancelBubble = true;
    });
    addEvent(window, 'focus', () => {
      _downKeys = [];
    });
    addEvent(element, 'keyup', (e) => {
      dispatch(e);
      clearModifier(e);
    });
  }
}

const _api = {
  setScope,
  getScope,
  deleteScope,
  getPressedKeyCodes,
  isPressed,
  filter,
  unbind,
  update
};
for (const a in _api) {
  if (Object.prototype.hasOwnProperty.call(_api, a)) {
    hotkeys[a] = _api[a];
  }
}

if (typeof window !== 'undefined') {
  const _hotkeys = window.hotkeys;
  hotkeys.noConflict = (deep) => {
    if (deep && window.hotkeys === hotkeys) {
      window.hotkeys = _hotkeys;
    }
    return hotkeys;
  };
  window.hotkeys = hotkeys;
}
// src/1-4.l2d.js
var L2D = {
    
    Loader: new Loader(),											// 资源加载器
	Keys: hotkeys,													// 键盘声明
	Bindings: {},													// 声明脚本绑定
	Temporary: {},													// 声明脚本临时数据
	Interface: {},													// 声明脚本接口
	Storage: {},													// 声明储存仓库
};

// src/2-1.equipment.js
class Equipment {
	constructor() {
		this._atk = 0;
		this._attackRange = 0;
		this._weight = 0;
		this._location = 0;
	}
	
	/**
	 * 获取装备攻击力
	 */
	get atk() {
		return this._atk;
	}
	
	/**
	 * 设置装备攻击力
	 */
	set atk(value) {
		this._atk = value;
	}
	
	/**
	 * 获取装备攻击范围加成
	 */
	get attackRange() {
		return this._attackRange;
	}
	
	/**
	 * 设置装备攻击范围加成
	 */
	set attackRange(value) {
		this._attackRange = value;
	}
	
	/**
	 * 获取装备重量
	 */
	get weight() {
		return this._weight;
	}
	
	/**
	 * 设置装备重量
	 */
	set weight(value) {
		this._weight = value;	
	}
	
	/**
	 * 获取装备位置
	 */
	get location() {
		return this._location;
	}
	
	/**
	 * 设置装备位置
	 */
	set location(value) {
		this._location = value;
	}
	
	/**
	 * 应用装备给角色
	 */
	apply(character) {
		// 获取已装备道具
		const equip = character.equipments[this.location];
		
		// 如果存在已装备道具则脱下(重新计算属性)
		if(!equip) {
			
			character.weight -= weight; 
		}
		
		// 重新计算角色属性
		character.weight += weight;
	}
}
// src/2-2.character.js
function updateEnityDirection(enity, hude) {
	const angle = 180 - Math.floor((hude * 180 / Math.PI) % 360);
	switch(true) {
		default: 
			enity.direction = 3;
			break;
		case angle > 45 && angle <= 135:
			enity.direction = 2;
			break;
		case angle > 135 && angle <= 225:
			enity.direction = 0;
			break;
		case angle > 225 && angle <= 315:
			enity.direction = 1;
			break;
	}
}

class Action {
}

class MoveTo extends Action {
	constructor(enity, path) {
		super();
		
		this.enity = enity;
		this.path = path;
	}
	
	update() {
		if(!this.path || this.path.length <= 0) {
			this.enity.action = null;
			this.enity.state = "idea";
			return;
		}
		
		// 计算速度
		const lastTarget = this.path[this.path.length - 1];
		const lastXDiff = lastTarget.x - this.enity.x;
		const lastYDiff = lastTarget.y - this.enity.y;
		const lastDistance = Math.sqrt(lastXDiff * lastXDiff + lastYDiff * lastYDiff);
		//if(lastDistance > 50)
		//	this.enity.speed = 1.5; 
		//else
		//	this.enity.speed = 0.5;
		
		// 计算距离
		const target = this.path[0];
		const speed = this.enity.speed;
		const xDiff = target.x - this.enity.x;
		const yDiff = target.y - this.enity.y;
		const distance = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
		if(distance > speed) { 
			const hude = xDiff == 0 && yDiff == 0 ? 0 : Math.atan2(xDiff, yDiff);
				   
			// 计算位置 
			Matter.Body.setVelocity(this.enity.body, {x:speed * Math.sin(hude),y:speed * Math.cos(hude)});
		}
		else { 
			//this.enity.x = target.x;
			//this.enity.y = target.y;
			this.path.splice(0,1);
		}			
	}
}

class TalkTo extends Action {
	constructor(enity, target) {
		super();
		
		this.enity = enity;
		this.target = target;
		this.moveAction = new MoveTo(enity, enity.scene.findPath(enity.position, target.position));
	}
	
	update() {
		// 检查与目标点距离
		const xDiff = this.target.x - this.enity.x;
		const yDiff = this.target.y - this.enity.y;
		const distance = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
		
		// 如果小于2个格子则进行对话
		if(distance < this.enity.scene.gridSize * 2) {
			
			// 结束动作
			this.enity.action = null;
			this.enity.state = `idea`;
			
			// 执行NPC对话
			if(L2D.Bindings[this.target.uuid]) 
				L2D.Bindings[this.target.uuid]();
		}
		else {
			
			// 更新移动逻辑
			this.moveAction.update();
		}	
	}
}

class AttackTo extends Action {
	constructor(enity, target) {
		super();
		
		this.enity = enity;
		this.target = target;
		this.moveAction = new MoveTo(enity, enity.scene.findPath(enity.position, target.position));
	}
	
	update() {
		// 检查与目标点距离
		const xDiff = this.target.x - this.enity.x;
		const yDiff = this.target.y - this.enity.y;
		const distance = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
		
		// 如果小于2个格子则进行对话
		if(distance < this.enity.scene.gridSize * 2) {		
			// 结束动作
			this.enity.state = `attack`;
			
			// 执行计算面对朝向
			const hude = xDiff == 0 && yDiff == 0 ? 0 :
				Math.atan2(xDiff, yDiff);			
			updateEnityDirection(this.enity, hude);
		}
		else {
			
			// 更新移动逻辑
			this.moveAction.update();
		}	
	}
}

/**
 * 直接攻击动作
 */
class Attack extends Action {
	constructor(enity) {
		this.enity = enity;
	}
	
	update() {
		
	}
}

var characterAnimations = {};
function getCharacterAnimation(state) {
	if(!characterAnimations[state])
		characterAnimations[state] = L2D.Loader.load(`res/actions/${state}.ani`);
		
	return characterAnimations[state];
}

class Character extends PIXI.Container {
	
	constructor() {
		super();
		this.size = 32;
		this.frame = 0;
		this.speed = 1;
		this.direction = 0;
		this.equipments = [];
		this._lastState = "";
		this._lastFrameTime = new Date().getTime();
	}
	
	moveTo(target, callback) {
		this.action = new MoveTo(this, this.scene.findPath(this.position, target), callback);
	}
	
	attackTo(target, callback) {
		this.action = new AttackTo(this, target, callback);
	}
	
	talkTo(target, callback) {
		this.action = new TalkTo(this, target, callback);
	}
	
	isEnemy(target) {
		return true;
	}
	
	get centerPoint() {
		return {x:this.x,y:this.y};
	}
	
	set centerPoint(val) {
		this.x = val.x;
		this.y = val.y;
	}
	
	get state() {
		return this._lastState;
	}
	 
	set state (state) {
		// 加载对应动画包
		if(this._lastState != state) {
			this._lastState = state;
			this.frame = 0;
			this.animation = getCharacterAnimation(state);
		}
	}
	
	update() { 
		// 更新动画帧
		if(new Date().getTime() - this._lastFrameTime > 200) {
			this._lastFrameTime = new Date().getTime();
			this.frame++; 
			if(this.animation.Frames && 
				this.frame >= this.animation.Frames.length) 
				this.frame = 0; 
		}
		else return;
		
		// 更新当前目标
		if(!this.Properties) return;
		if(!this.animation || !this.animation.Frames) return;
		if(this.frame >= this.animation.Frames.length) return;
		
		var drawCalls = [];
		var frameInfo = this.animation.Frames[this.frame];
		for(var i = 0; i < frameInfo.Fragments.length; i++) {
		    var frag = frameInfo.Fragments[i];
		    drawCalls.push( 
		    {
		        location : frag.Locations[this.direction],
		        fragment : frag
		    });
		}
		
		drawCalls.sort(function(a,b) 
		{ 
		    return b.location.Z - a.location.Z;
		}); 
		
		while(this.children.length > 0)
			this.removeChildAt(0);
			
		for(var i = 0; i < drawCalls.length; i++) {
		    var dc = drawCalls[i];
		    var style = this.Properties.Style[dc.fragment.Folder];
		    if(style) {
		        var width = 0;
		        switch(dc.fragment.Folder) {
		            case "adornment": width = 18; break;
		            case "armor": width = 32; break;
		            case "body": width = 32; break;
		            case "bodyback": width = 32; break;
		            case "hand": width = 16; break;
		            case "head": width = 32; break;
		            case "leg": width = 16; break;
		            case "shoulder": width = 32; break; 
		            case "weapon": width = 64; break;
		            default: width = 32; break;
		        }
				
		        var halfWidth = width / 2;
		        var cc = dc.location;
		        var url = "res/role/" + dc.fragment.Folder + "/" + style;
		        var texture = PIXI.Texture.from(url);
				
				const gs = this.scene.gridSize / 2;
				const source = new PIXI.Texture(texture,  { x:width * cc.Image, y:0, width:width, height:width });
				const bunny = new PIXI.Sprite(source);
				bunny.x = cc.X - halfWidth;
				bunny.y = cc.Y - halfWidth - this.direction * 72 - 16 + gs;
				bunny.scale = this.scale;
				bunny.angle = this.angle;
				this.addChild(bunny);
		    }
		}
	}
}

function CharacterLoader(loader) {
    this.loader = loader;
}
CharacterLoader.prototype = {
    responseType : 'text',
    load : function(buffer, params, entry) {
        var jsonObj = JSON.parse(buffer);
        for(var member in jsonObj)
            entry[member] = jsonObj[member];

        return entry;
    },
    create : function() {
        return new Character(); 
    }
}

function AnimationLoader(loader) {
    this.loader = loader;
}
AnimationLoader.prototype = {
    responseType : 'text',
    load : function(buffer, params, entry) {
        var jsonObj = JSON.parse(buffer);
        for(var member in jsonObj)
            entry[member] = jsonObj[member];

        return entry;
    },
    create : function() {
        return {};
    }
}

L2D.Loader.addMode("act", new CharacterLoader(this));
L2D.Loader.addMode("ani", new AnimationLoader(this));
// src/2-3.navmesh.js
!function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?exports.NavMesh=n():t.NavMesh=n()}("undefined"!=typeof self?self:this,(function(){return(()=>{var t={774:(t,n)=>{var i,s,o,e;e=function(){function t(t){for(var n=t,i=[];n.parent;)i.unshift(n),n=n.parent;return i}var n={search:function(i,s,e,r){i.cleanDirty();var h=(r=r||{}).heuristic||n.heuristics.manhattan,c=r.closest||!1,a=new o((function(t){return t.f})),u=s;for(s.h=h(s,e),i.markDirty(s),a.push(s);a.size()>0;){var l=a.pop();if(l===e)return t(l);l.closed=!0;for(var p=i.neighbors(l),d=0,f=p.length;d<f;++d){var g=p[d];if(!g.closed&&!g.isWall()){var y=l.g+g.getCost(l),x=g.visited;(!x||y<g.g)&&(g.visited=!0,g.parent=l,g.h=g.h||h(g,e),g.g=y,g.f=g.g+g.h,i.markDirty(g),c&&(g.h<u.h||g.h===u.h&&g.g<u.g)&&(u=g),x?a.rescoreElement(g):a.push(g))}}}return c?t(u):[]},heuristics:{manhattan:function(t,n){return Math.abs(n.x-t.x)+Math.abs(n.y-t.y)},diagonal:function(t,n){var i=Math.sqrt(2),s=Math.abs(n.x-t.x),o=Math.abs(n.y-t.y);return 1*(s+o)+(i-2)*Math.min(s,o)}},cleanNode:function(t){t.f=0,t.g=0,t.h=0,t.visited=!1,t.closed=!1,t.parent=null}};function i(t,n){n=n||{},this.nodes=[],this.diagonal=!!n.diagonal,this.grid=[];for(var i=0;i<t.length;i++){this.grid[i]=[];for(var o=0,e=t[i];o<e.length;o++){var r=new s(i,o,e[o]);this.grid[i][o]=r,this.nodes.push(r)}}this.init()}function s(t,n,i){this.x=t,this.y=n,this.weight=i}function o(t){this.content=[],this.scoreFunction=t}return i.prototype.init=function(){this.dirtyNodes=[];for(var t=0;t<this.nodes.length;t++)n.cleanNode(this.nodes[t])},i.prototype.cleanDirty=function(){for(var t=0;t<this.dirtyNodes.length;t++)n.cleanNode(this.dirtyNodes[t]);this.dirtyNodes=[]},i.prototype.markDirty=function(t){this.dirtyNodes.push(t)},i.prototype.neighbors=function(t){var n=[],i=t.x,s=t.y,o=this.grid;return o[i-1]&&o[i-1][s]&&n.push(o[i-1][s]),o[i+1]&&o[i+1][s]&&n.push(o[i+1][s]),o[i]&&o[i][s-1]&&n.push(o[i][s-1]),o[i]&&o[i][s+1]&&n.push(o[i][s+1]),this.diagonal&&(o[i-1]&&o[i-1][s-1]&&n.push(o[i-1][s-1]),o[i+1]&&o[i+1][s-1]&&n.push(o[i+1][s-1]),o[i-1]&&o[i-1][s+1]&&n.push(o[i-1][s+1]),o[i+1]&&o[i+1][s+1]&&n.push(o[i+1][s+1])),n},i.prototype.toString=function(){for(var t=[],n=this.grid,i=0;i<n.length;i++){for(var s=[],o=n[i],e=0;e<o.length;e++)s.push(o[e].weight);t.push(s.join(" "))}return t.join("\n")},s.prototype.toString=function(){return"["+this.x+" "+this.y+"]"},s.prototype.getCost=function(t){return t&&t.x!=this.x&&t.y!=this.y?1.41421*this.weight:this.weight},s.prototype.isWall=function(){return 0===this.weight},o.prototype={push:function(t){this.content.push(t),this.sinkDown(this.content.length-1)},pop:function(){var t=this.content[0],n=this.content.pop();return this.content.length>0&&(this.content[0]=n,this.bubbleUp(0)),t},remove:function(t){var n=this.content.indexOf(t),i=this.content.pop();n!==this.content.length-1&&(this.content[n]=i,this.scoreFunction(i)<this.scoreFunction(t)?this.sinkDown(n):this.bubbleUp(n))},size:function(){return this.content.length},rescoreElement:function(t){this.sinkDown(this.content.indexOf(t))},sinkDown:function(t){for(var n=this.content[t];t>0;){var i=(t+1>>1)-1,s=this.content[i];if(!(this.scoreFunction(n)<this.scoreFunction(s)))break;this.content[i]=n,this.content[t]=s,t=i}},bubbleUp:function(t){for(var n=this.content.length,i=this.content[t],s=this.scoreFunction(i);;){var o,e=t+1<<1,r=e-1,h=null;if(r<n){var c=this.content[r];(o=this.scoreFunction(c))<s&&(h=r)}if(e<n){var a=this.content[e];this.scoreFunction(a)<(null===h?s:o)&&(h=e)}if(null===h)break;this.content[t]=this.content[h],this.content[h]=i,t=h}}},{astar:n,Graph:i}},"object"==typeof t.exports?t.exports=e():(s=[],void 0===(o="function"==typeof(i=e)?i.apply(void 0,s):i)||(t.exports=o))},636:(t,n,i)=>{"use strict";i.d(n,{default:()=>g});var s=i(774),o=i.n(s);class e{constructor(t=0,n=0){this.x=t,this.y=n}equals(t){return this.x===t.x&&this.y===t.y}angle(t){return Math.atan2(t.y-this.y,t.x-this.x)}distance(t){const n=t.x-this.x,i=t.y-this.y;return Math.sqrt(n*n+i*i)}add(t){this.x+=t.x,this.y+=t.y}subtract(t){this.x-=t.x,this.y-=t.y}clone(){return new e(this.x,this.y)}}class r{constructor(t,n){this.weight=1,this.x=0,this.y=0,this.id=t,this.polygon=n,this.edges=n.edges,this.neighbors=[],this.portals=[],this.centroid=this.calculateCentroid(),this.boundingRadius=this.calculateRadius()}getPoints(){return this.polygon.points}contains(t){return this.polygon.contains(t.x,t.y)||this.isPointOnEdge(t)}calculateCentroid(){const t=new e(0,0),n=this.polygon.points.length;return this.polygon.points.forEach((n=>t.add(n))),t.x/=n,t.y/=n,t}calculateRadius(){let t=0;for(const n of this.polygon.points){const i=this.centroid.distance(n);i>t&&(t=i)}return t}isPointOnEdge({x:t,y:n}){for(const i of this.edges)if(i.pointOnSegment(t,n))return!0;return!1}destroy(){this.neighbors=[],this.portals=[]}toString(){return`NavPoly(id: ${this.id} at: ${this.centroid})`}isWall(){return 0===this.weight}centroidDistance(t){return this.centroid.distance(t.centroid)}getCost(t){return this.centroidDistance(t)}}function h(t,n){const i=n.start,s=n.end,o=function(t,n){const i=n.x-t.x,s=n.y-t.y;return i*i+s*s}(i,s);let r=((t.x-i.x)*(s.x-i.x)+(t.y-i.y)*(s.y-i.y))/o;var h;return(h=r)<0&&(h=0),h>1&&(h=1),r=h,new e(i.x+r*(s.x-i.x),i.y+r*(s.y-i.y))}function c(t,n,i){const s=n.x-t.x,o=n.y-t.y;return(i.x-t.x)*o-s*(i.y-t.y)}function a(t,n,i=1e-4){return Math.abs(t-n)<=i}function u(t,n){let i=t-n;const s=i+Math.PI,o=2*Math.PI;return i=s-Math.floor(s/o)*o,i-=Math.PI,i}function l(t,n,i=1e-4){const s=c(t.start,t.end,n.start),o=c(t.start,t.end,n.end);return!(!a(s,0,i)||!a(o,0,i))}class p{constructor(){this.portals=[],this.path=[]}push(t,n){void 0===n&&(n=t),this.portals.push({left:t,right:n})}stringPull(){const t=this.portals,n=[];let i=0,s=0,o=0,e=t[0].left,r=t[0].left,h=t[0].right;n.push(e);for(var a=1;a<t.length;a++){const u=t[a].left,l=t[a].right;if(c(e,h,l)<=0){if(!(e.equals(h)||c(e,r,l)>0)){n.push(r),e=r,i=s,r=e,h=e,s=i,o=i,a=i;continue}h=l,o=a}if(c(e,r,u)>=0){if(!(e.equals(r)||c(e,h,u)<0)){n.push(h),e=h,i=o,r=e,h=e,s=i,o=i,a=i;continue}r=u,s=a}}return 0!==n.length&&n[n.length-1].equals(t[t.length-1].left)||n.push(t[t.length-1].left),this.path=n,n}}class d{constructor(t,n,i,s){this.start=new e(t,n),this.end=new e(i,s),this.left=Math.min(t,i),this.right=Math.max(t,i),this.top=Math.min(n,s),this.bottom=Math.max(n,s)}pointOnSegment(t,n){return t>=this.left&&t<=this.right&&n>=this.top&&n<=this.bottom&&this.pointOnLine(t,n)}pointOnLine(t,n){return(t-this.left)*(this.bottom-this.top)==(this.right-this.left)*(n-this.top)}}class f{constructor(t,n=!0){this.isClosed=n,this.points=t,this.edges=[];for(let n=1;n<t.length;n++){const i=t[n-1],s=t[n];this.edges.push(new d(i.x,i.y,s.x,s.y))}if(this.isClosed){const n=t[0],i=t[t.length-1];this.edges.push(new d(n.x,n.y,i.x,i.y))}}contains(t,n){let i=!1;for(let s=-1,o=this.points.length-1;++s<this.points.length;o=s){const e=this.points[s].x,r=this.points[s].y,h=this.points[o].x,c=this.points[o].y;(r<=n&&n<c||c<=n&&n<r)&&t<(h-e)*(n-r)/(c-r)+e&&(i=!i)}return i}}const g=class{constructor(t,n=0){this.meshShrinkAmount=n;const i=t.map((t=>{const n=t.map((t=>new e(t.x,t.y)));return new f(n)}));this.navPolygons=i.map(((t,n)=>new r(n,t))),this.calculateNeighbors(),this.graph=new class{constructor(t){this.grid=[],this.init=o().Graph.prototype.init.bind(this),this.cleanDirty=o().Graph.prototype.cleanDirty.bind(this),this.markDirty=o().Graph.prototype.markDirty.bind(this),this.toString=o().Graph.prototype.toString.bind(this),this.nodes=t,this.init()}neighbors(t){return t.neighbors}navHeuristic(t,n){return t.centroidDistance(n)}destroy(){this.cleanDirty(),this.nodes=[]}}(this.navPolygons)}getPolygons(){return this.navPolygons}destroy(){this.graph.destroy();for(const t of this.navPolygons)t.destroy();this.navPolygons=[]}isPointInMesh(t){return this.navPolygons.some((n=>n.contains(t)))}findClosestMeshPoint(t,n=Number.POSITIVE_INFINITY){let i=n,s=null,o=null;for(const n of this.navPolygons){if(n.contains(t)){i=0,s=n,o=t;break}const e=n.boundingRadius;if(n.centroid.distance(t)-e<i){const e=this.projectPointToPolygon(t,n);e.distance<i&&(i=e.distance,s=n,o=e.point)}}return{distance:i,polygon:s,point:o}}findPath(t,n){let i,s,r=null,h=null,c=Number.MAX_VALUE,a=Number.MAX_VALUE;const u=new e(t.x,t.y),l=new e(n.x,n.y);for(const t of this.navPolygons)s=t.boundingRadius,i=t.centroid.distance(u),i<=c&&i<=s&&t.contains(u)&&(r=t,c=i),i=t.centroid.distance(l),i<=a&&i<=s&&t.contains(l)&&(h=t,a=i);if(!h&&this.meshShrinkAmount>0)for(const t of this.navPolygons)if(s=t.boundingRadius+this.meshShrinkAmount,i=t.centroid.distance(l),i<=s){const{distance:n}=this.projectPointToPolygon(l,t);n<=this.meshShrinkAmount&&n<a&&(h=t,a=n)}if(!h)return null;if(!r&&this.meshShrinkAmount>0)for(const t of this.navPolygons)if(s=t.boundingRadius+this.meshShrinkAmount,i=t.centroid.distance(u),i<=s){const{distance:n}=this.projectPointToPolygon(u,t);n<=this.meshShrinkAmount&&n<c&&(r=t,c=n)}if(!r)return null;if(r===h)return[u,l];const d=o().astar.search(this.graph,r,h,{heuristic:this.graph.navHeuristic});if(0===d.length)return null;d.unshift(r);const f=new p;f.push(u);for(let t=0;t<d.length-1;t++){const n=d[t],i=d[t+1];let s=null;for(let t=0;t<n.neighbors.length;t++)n.neighbors[t].id===i.id&&(s=n.portals[t]);if(!s)throw new Error("Path was supposed to be found, but portal is missing!");f.push(s.start,s.end)}f.push(l),f.stringPull();let g=null;const y=[];for(const t of f.path){const n=t.clone();g&&n.equals(g)||y.push(n),g=n}return y}calculateNeighbors(){for(let t=0;t<this.navPolygons.length;t++){const n=this.navPolygons[t];for(let i=t+1;i<this.navPolygons.length;i++){const t=this.navPolygons[i];if(!(n.centroid.distance(t.centroid)>n.boundingRadius+t.boundingRadius))for(const i of n.edges)for(const s of t.edges){if(!l(i,s))continue;const o=this.getSegmentOverlap(i,s);if(!o)continue;n.neighbors.push(t),t.neighbors.push(n);const[e,r]=o;let h=n.centroid.angle(i.start),c=n.centroid.angle(o[0]),a=n.centroid.angle(o[1]),p=u(h,c),f=u(h,a);p<f?n.portals.push(new d(e.x,e.y,r.x,r.y)):n.portals.push(new d(r.x,r.y,e.x,e.y)),h=t.centroid.angle(s.start),c=t.centroid.angle(o[0]),a=t.centroid.angle(o[1]),p=u(h,c),f=u(h,a),p<f?t.portals.push(new d(e.x,e.y,r.x,r.y)):t.portals.push(new d(r.x,r.y,e.x,e.y))}}}}getSegmentOverlap(t,n){const i=[{line:t,point:t.start},{line:t,point:t.end},{line:n,point:n.start},{line:n,point:n.end}];i.sort((function(t,n){return t.point.x<n.point.x?-1:t.point.x>n.point.x?1:t.point.y<n.point.y?-1:t.point.y>n.point.y?1:0}));const s=i[0].line===i[1].line,o=i[1].point.equals(i[2].point);return s||o?null:[i[1].point,i[2].point]}projectPointToPolygon(t,n){let i=null,s=Number.MAX_VALUE;for(const o of n.edges){const n=h(t,o),e=t.distance(n);(null===i||e<s)&&(s=e,i=n)}return{point:i,distance:s}}}}},n={};function i(s){if(n[s])return n[s].exports;var o=n[s]={exports:{}};return t[s](o,o.exports,i),o.exports}return i.n=t=>{var n=t&&t.__esModule?()=>t.default:()=>t;return i.d(n,{a:n}),n},i.d=(t,n)=>{for(var s in n)i.o(n,s)&&!i.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:n[s]})},i.o=(t,n)=>Object.prototype.hasOwnProperty.call(t,n),i(636)})().default}));
//# sourceMappingURL=navmesh.js.map
// src/2-4.scene.js
class SceneImage extends PIXI.Container {
	 constructor(gridSize, scene) { 
		super(); 
		
		this.scene = scene;
		this.gridSize = gridSize;
	 }
	 update() {

		var alpha = 1;	
		if(this.scene.player && this.isCylinder)
			if(this.scene.player.x > this.x - 8 && this.scene.player.x < this.x + this.width &&
			   this.scene.player.y > this.y && this.scene.player.y < this.y + this.height - this.gridSize/2)
				alpha = alpha * 0.7;

		this.alpha = alpha;
	 }
	 
	 get centerPoint() {
		 return {x:this.x+this.width/2,y:this.y+this.height-this.gridSize/2};
	 }
	 
	 set centerPoint(val) {
	 	this.x = val.x - this.width/2;
	 	this.y = val.y - this.height+this.gridSize/2;
	 }
}

class Scene extends PIXI.Container {
	constructor() {
		super();
		this.images = [];
	}
	
	update () {
		if(!this.checksum) return;
		
		var blocks = [];
		var gs = this.gridSize;
		
		// 绘制图形
		for(var i = 0; i < this.images.length; i++)
			this.images[i].update();
		
		// 更新实体状态
		for(var i = 0; i < this.children.length; i++) {
			const sprite = this.children[i];
			if(sprite.update) sprite.update();
		}
		
		// 计算圆柱实体Z轴
		this.children.sort((a, b) => 
		{
			if(a.isCylinder && b.isCylinder) {
				var aa = a.centerPoint;
				var bb = b.centerPoint;
				if (aa.y > bb.y) return 1;
				if (aa.y < bb.y) return -1;
				if (aa.x > bb.x) return 1;
				if (aa.x < bb.x) return -1;	
			} 
			
			if(a.isCylinder) return 1;
			if(b.isCylinder) return -1;
			return 0;
		});
		
		// 更新实体动作
		for(var i = 0; i < this.children.length; i++) {			
			var enity = this.children[i];
			if(!enity.isCylinder) 
				continue;
					
			if(enity.action) 
				enity.action.update();
		}
		
		// 如果存在战场则更新战场
		if(this.battle) this.battle.update();
	}

	addChild (child) {
		super.addChild(child);
		child.scene = this;
	}

	/**
	 * 寻路
	 * @param {Object} src 当前坐标
	 * @param {Object} dst 目标坐标
	 */
	findPath(src, dst) {
		var path = this.navmesh.findPath(src, dst);
		if(path) path.splice(0,1);
		return path;
	}
	
	/**
	 * 开启战场
	 * @param {Object} loc 战场开启坐标
	 * @param {Object} radius 战场半径
	 */
	fight(loc, radius) {
		// 首先计算开战时范围内所有角色对主角的敌意
		const enemies = [];
		const childrens = [];
		for(var i = 0; i < this.children.length; i++) {
			const child = this.children[i];
			// 如果子项是圆柱体
			if(child.isCylinder) {
				// 计算与战场距离
				const xDiff = child.x - loc.x;
				const yDiff = child.y - loc.y;
				const distance = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
				// 计算距离是否小于战场半径
				if(distance <= radius) {
					// 如果单位存在敌意则加入到战场敌人列表中
					if(child.isEnemy && child.isEnemy(player) && child != player)
						enemies.push(child);

					// 全部加入子集合以便后续触发战场事件
					childrens.push(child);
				}
			}
		}
		
		// 新建战场
		const battle = new Battle(this, enemies);
		// 触发战场事件
		//for(var i = 0; i < childrens.length; i++)
		//	childrens[i].dispatchEvent(new CustomEvent("battle"), { battle });
			
		// 赋予该场景战场属性
		this.battle = battle;
	}
}

function SceneLoader(loader) {
    this.loader = loader;
} 
SceneLoader.prototype = {
    responseType : 'text',
    load : function(buffer, params, entry) {
        var jsonObj = JSON.parse(buffer);
		entry.checksum = jsonObj.Inculde;
		entry.gridSize = jsonObj.GridSize;
		
		const texture = PIXI.Texture.from(`res/${jsonObj.Inculde}`);
		const columns = jsonObj.BitmapWidth / jsonObj.GridSize;
		// 添加地表
		const tiles = new PIXI.Container();
		tiles.zIndex = -1;
		for(var i = jsonObj.Cells.length - 1; i >= 0; i--) {
		    const cell = jsonObj.Cells[i];
		    const cellX = cell.X * jsonObj.GridSize;
		    const cellY = cell.Y * jsonObj.GridSize;
		    const srcX = (cell.Image % columns) * jsonObj.GridSize + 1;
		    const srcY = Math.floor(cell.Image / columns) * jsonObj.GridSize + 1;
			
			// 绘制贴图 
			const source = new PIXI.Texture(texture,  { x:srcX, y:srcY, width:jsonObj.GridSize-2, height:jsonObj.GridSize-2 });
			const bunny = new PIXI.Sprite(source);
			bunny.x = cellX;
			bunny.y = cellY;
			bunny.width = jsonObj.GridSize;
			bunny.height = jsonObj.GridSize;
			tiles.addChild(bunny);
		}
		entry.addChild(tiles);
				
		// 添加图形组件
		for(var i = 0; i < jsonObj.Images.length; i++) {
			var jsonImg = jsonObj.Images[i];
			const srcX = (jsonImg.Lower % columns) * jsonObj.GridSize;
			const srcY = Math.floor(jsonImg.Lower / columns) * jsonObj.GridSize;
			const dstX = (jsonImg.Upper % columns) * jsonObj.GridSize + jsonObj.GridSize;
			const dstY = Math.floor(jsonImg.Upper / columns) * jsonObj.GridSize + jsonObj.GridSize;
			const width = dstX - srcX;
			const height = dstY - srcY;
			
			var img = new SceneImage(jsonObj.GridSize, entry);
			
			const source = new PIXI.Texture(texture,  { x:srcX + 1, y:srcY + 1, width:width - 2, height:height - 2 });
			const bunny = new PIXI.Sprite(source);
			bunny.width = width;
			bunny.height = height;
			bunny.scale = { x:jsonImg.Transform.ScaleX, y:jsonImg.Transform.ScaleY };
			bunny.angle = jsonImg.Transform.Angle;
			bunny.tint = {r:jsonImg.Color.R,g:jsonImg.Color.G,b:jsonImg.Color.B};
			
			img.x = jsonImg.Transform.X * jsonObj.GridSize;
			img.y = jsonImg.Transform.Y * jsonObj.GridSize;
			img.width = width;
			img.heght = height;
			img.size = width;
			img.alpha = jsonImg.Color.A/255;
			img.isCylinder = jsonImg.IsCylinder;	
			img.addChild(bunny);
			entry.addChild(img);
		}
		
		// 添加npc组件
		for(var i = 0; i < jsonObj.Npcs.length; i++) {
			var jsonNpc = jsonObj.Npcs[i];
			// 加载NPC脚本
			window.require(jsonNpc.Script);
			// 新建NPC
			var npc = L2D.Loader.load(`res/${jsonNpc.Style}`);
			// 为NPC添加动作
			npc.state = `idea`;
			// 为npcs设置坐标等信息
			npc.x = jsonNpc.Transform.X * jsonObj.GridSize;
			npc.y = jsonNpc.Transform.Y * jsonObj.GridSize;
			npc.scale = { x:jsonNpc.Transform.ScaleX, y:jsonNpc.Transform.ScaleY };
			npc.angle = jsonNpc.Transform.Angle;
			npc.tint = {r:jsonNpc.Color.R,g:jsonNpc.Color.G,b:jsonNpc.Color.B};
			npc.isCylinder = true;
			npc.uuid = jsonNpc.Wid;
			npc.name = jsonNpc.Header;
			// 添加到实体组件内
			entry.addChild(npc);
		}

		// 添加阻挡区域
		var meshPolygonPoints = [];
		var blocks = [];
		for(var yy = 0; yy < jsonObj.GridMap.length; yy++) {
			const row = jsonObj.GridMap[yy];
			for(var xx = 0; xx < row.length; xx++) {
				const x = yy*entry.gridSize;
				const y = xx*entry.gridSize;
				const w = entry.gridSize;
				const h = entry.gridSize;
					
				if(row[xx]) 
					meshPolygonPoints.push([{x,y},{x:x+w,y},{x:x+w,y:y+h},{x,y:y+h}]);
				else
					blocks.push({x,y,width:w,height:h});
			}
		}
		entry.blocks = blocks;
		entry.navmesh = new NavMesh(meshPolygonPoints);
    },
	create : function() {
	    return new Scene();  
	}
};

L2D.Loader.addMode("gat", new SceneLoader(this));
// src/2-5.ui.js
class Talk {
	constructor(uimanager, talker, context, option, callback) {
		this.uimanager = uimanager;
		this.callback = callback;
		this.container = new PIXI.Container();
		
		// 寻找talker坐标
		var sprite;
		for(var i = 0; i < scene.children.length; i++)
			if(scene.children[i].name == talker) 
				sprite = scene.children[i];
				
		// 如果未找到对话者则跳过
		if(!sprite) {
			 this.callback(this);
			 return;
		}

		// 创建对话框遮罩
		const frameMask = new PIXI.Graphics();
		frameMask.beginFill(0x000000, 0.000001);
		frameMask.drawRect(0, 0, 8000, 8000);
		frameMask.endFill();

		// 创建对话框
		const frame = new PIXI.smooth.SmoothGraphics();
		frame.beginFill(0x000000, 0.7);
		frame.lineStyle({ color: 0xffffff, width: 1, alignment: 0 });
		frame.moveTo(20,  0).lineTo(350, 10).lineTo(380, 40).
			lineTo(350, 95).lineTo(198, 98).lineTo(196, 110).
			lineTo(180, 118).lineTo(187, 108).lineTo(185, 99).
			lineTo(20, 100).lineTo(0, 60).lineTo(20, 0);
			
		const frameX = (sprite.x - camera.pivot.x) * camera.scale.x + camera.x - 168;
		const frameY = (sprite.y - camera.pivot.y) * camera.scale.y + camera.y - 133;
		frame.position.set(frameX, frameY);
		
		// 设置对话位置
		const style = new PIXI.TextStyle({
		    fontFamily: 'Arial',
		    fontSize: 16,
		    fill: ['#ffffff'], // gradient
		    wordWrap: true,
			breakWords:true,
		    wordWrapWidth: 240,
		    lineJoin: 'round',
		});
		
		const richTextSize = PIXI.TextMetrics.measureText(context, style);
		const richText = new PIXI.Text(context, style);
		richText.x = 0;
		richText.y = 0;
		richText.position.set(frame.position.x + 30, frame.position.y + (100 - richTextSize.height) / 2);
		
		// 添加一个对话框容器
		this.container.addChild(frameMask);
		this.container.addChild(frame);
		this.container.addChild(richText);
		this.container.eventMode = 'static';
		//this.container.cursor = 'pointer';
		this.container.on('pointerdown', (event) => {
			this.end();
		});
		
		this.uimanager.addChild(this.container);
	}
	
	end () {
		this.uimanager.removeChild(this.container);
		if (this.callback) this.callback(this);
	}
} 

class Menu {
	constructor(uimanager, items, talker, context, option, callback) {
		this.uimanager = uimanager;
		this.callback = callback;
		this.container = new PIXI.Container();
		
		// 绘制最后一个对话
		// 寻找talker坐标
		var sprite;
		for(var i = 0; i < scene.children.length; i++)
			if(scene.children[i].name == talker) 
				sprite = scene.children[i];
				
		// 如果未找到对话者则跳过
		if(!sprite) {
			 this.callback(this);
			 return;
		}
		
		// 得到talker半身像
		// const spriteImage = new PIXI.Sprite(sprite.image);
		
		// 创建对话框遮罩
		const frameMask = new PIXI.Graphics();
		frameMask.beginFill(0x000000, 0.000001);
		frameMask.drawRect(0, 0, 8000, 8000);
		frameMask.endFill();
		
		// 创建对话框
		const frame = new PIXI.smooth.SmoothGraphics();
		frame.beginFill(0x000000, 0.7);
		frame.lineStyle({ color: 0xffffff, width: 1, alignment: 0 });
		frame.moveTo(20,  0).lineTo(350, 10).lineTo(380, 40).
			lineTo(350, 95).lineTo(198, 98).lineTo(196, 110).
			lineTo(180, 118).lineTo(187, 108).lineTo(185, 99).
			lineTo(20, 100).lineTo(0, 60).lineTo(20, 0);
			
		const frameX = (sprite.x - camera.pivot.x) * camera.scale.x + camera.x - 168;
		const frameY = (sprite.y - camera.pivot.y) * camera.scale.y + camera.y - 133;
		frame.position.set(frameX, frameY);
		
		// 设置对话位置
		const style = new PIXI.TextStyle({
		    fontFamily: 'Arial',
		    fontSize: 16,
		    fill: ['#ffffff'], // gradient
		    wordWrap: true,
			breakWords:true,
		    wordWrapWidth: 240,
		    lineJoin: 'round',
		});
		
		const richTextSize = PIXI.TextMetrics.measureText(context, style);
		const richText = new PIXI.Text(context, style);
		richText.x = 0;
		richText.y = 0;
		richText.position.set(frame.position.x + 30, frame.position.y + (100 - richTextSize.height) / 2);
		
		// 添加一个对话框容器
		this.container.addChild(frameMask);
		this.container.addChild(frame);
		this.container.addChild(richText);
	
		var offsetY = frameY + 105;
		for(var i = 0; i < items.length; i++) {
			var item = items[i];
			
			// button框
			const frame = new PIXI.smooth.SmoothGraphics();
			frame.index = i;
			frame.beginFill(0x000000, 0.7);
			frame.lineStyle({ color: 0xffffff, width: 1, alignment: 0 });
			frame.moveTo(0,0).lineTo(160, 0).lineTo(170, 52).lineTo(10, 52).lineTo(0, 0);
			frame.on('pointerdown', (evt) => {
				this.callback(evt.currentTarget.index);
				this.uimanager.removeChild(this.container);
			});
			frame.eventMode = 'static';
			frame.cursor = 'pointer';
			frame.position.set(frameX + 210, offsetY);
			
			// 设置文字
			const style = new PIXI.TextStyle({
			    fontFamily: 'Arial',
			    fontSize: 16,
			    fill: ['#ffffff'], // gradient
			    wordWrap: true,
			    wordWrapWidth: 140,
				breakWords:true,
			    lineJoin: 'round',
			});
			
			const richTextSize = PIXI.TextMetrics.measureText(item, style);
			const richText = new PIXI.Text(item, style);
			richText.x = 0;
			richText.y = 0;
			richText.position.set(frame.position.x + (170 - richTextSize.width) / 2, frame.position.y + (52 - richTextSize.height) / 2);		
			
			this.container.addChild(frame);
			this.container.addChild(richText);
			
			offsetY = offsetY + 62;
		}
		
		this.uimanager.addChild(this.container);
	}
}

class UIManager extends PIXI.Container {
	constructor() {
		super();
	}
	
	talk (talker, context, option) {
		this.lastTalker = talker;
		this.lastContext = context;
		return new Promise(resolve => new Talk(this, talker, context, option, resolve));
	}
	
	menu (items, option) {
		return new Promise(resolve => new Menu(this, items, this.lastTalker, this.lastContext, option, resolve));
	} 

	update() {
		for(var i = 0; i < this.children.length; i++)
			if(this.children[i].update)
				this.children[i].update();
	}
}

L2D.UIManager = new UIManager();
L2D.UserInterface = new PIXI.Container();
// src/3-1.battle.js
/**
 * 战场
 * 战场会以当前场景为初始化
 */
class Battle {
	constructor(scene, enemies) {
		this.scene = scene;
		this.enemies = enemies;
		
		// 新建碰撞引擎
		const engine = Matter.Engine.create();
		const render = Matter.Render.create({ element: document.body, engine:engine, options: {
            showVelocity: true,
            showAngleIndicator: true,
			width:480,
			height:480
        }});
		
		engine.gravity.y = 0;
		engine.gravity.x = 0;
		
		// 将碰撞物体添加到引擎内
		const bodies = [];
		for(var i = 0; i < scene.children.length; i++) {
			const child = scene.children[i];
			if(child.isCylinder && child.constructor.name=="Character") {
				const cpts = child.centerPoint;
				child.body = Matter.Bodies.circle(cpts.x, cpts.y, child.size/4, {isStatic:false, mass:Math.random(100)});
				bodies.push(child.body);
			}
		}
		
		// 将阻挡物设置为静态
		for(var i = 0; i < scene.blocks.length; i++) {
			const block = scene.blocks[i];
			bodies.push(Matter.Bodies.rectangle(block.x+block.width/2, block.y+block.height/2, block.width, block.height, {isStatic:true}));
		}
		
		Matter.Composite.add(engine.world, bodies);// 将所有物体添加到世界中			
		Matter.Render.run(render);//运行渲染器
		var runner = Matter.Runner.create();
		Matter.Runner.run(runner, engine);
		
		Matter.Render.lookAt(render, {
			min: { x: 0, y: 0 },
			max: { x: 480, y: 480 }
		});
		
		// 新建箭头
		const arrow = new PIXI.smooth.SmoothGraphics();
		arrow.beginFill(0xe1de81, 1);
		arrow.drawCircle(0, -30, 3);
		arrow.drawCircle(0, -42, 3);
		arrow.drawCircle(0, -54, 3);
		arrow.moveTo(0, -68).lineTo(-15, -64).lineTo(-13, -62).lineTo(0, -65).
			lineTo(13, -62).lineTo(15, -64).lineTo(0, -68);
		arrow.isCylinder = true;
		arrow.centerPoint = {x:arrow.x,y:arrow.y};
		
		// 添加碰撞事件
		Matter.Events.on(engine, 'collisionStart', function(e) {
			// 读取碰撞块
			for(var i = 0; i < e.pairs.length; i++) {
				const pair = e.pairs[i];
				const bodyA = pair.bodyA;
				const bodyB = pair.bodyB;
				
			}
		});

		// 将箭头加入场景
		this.scene.addChild(arrow);
		this.arrow = arrow;
	}
	
	/**
	 * 更新回合
	 * 再更新回合时会处理用户输入和游戏实体的AI
	 */
	update() {
		// 同步更新body
		for(var i = 0; i < scene.children.length; i++) {
			const child = scene.children[i];
			if(child.isCylinder && child.body) {
				const xDiff = child.body.position.x - child.centerPoint.x;
				const yDiff = child.body.position.y - child.centerPoint.y;
				const distance = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
				const hude = Math.atan2(xDiff, yDiff);
				updateEnityDirection(child, hude);
				child.state = distance > 2 ? "run":distance < 0.08 ?'idea':"walk";
				child.centerPoint = {x:child.body.position.x,y:child.body.position.y};
			}
		}
		
		// 让箭头跟随角色
		this.arrow.x = this.scene.player.x;
		this.arrow.y = this.scene.player.y - 8;
		
		// 计算箭头方向
		const xDiff = this.arrow.x - this.arrow.centerPoint.x;
		const yDiff = this.arrow.y - this.arrow.centerPoint.y;
		const hude = xDiff == 0 && yDiff == 0 ? 0 : Math.atan2(xDiff, yDiff);
		const angle = 180 - Math.floor((hude * 180 / Math.PI) % 360);
		this.arrow.angle = angle;	
		this.arrow.centerPoint = {x:this.arrow.x,y:this.arrow.y};
		
		// 再进行AI更新,通知所有敌人去攻击主角
		for(var i = 0; i < this.enemies.length; i++)
			this.enemies[i].attackTo(this.scene.player);
	}
	
	/**
	 * 战场结束
	 */
	end() {
		this.scene.removeChild(arrow);
		this.scene.battle = null;
	}
}
return L2D;
})();
